package com.tm.util;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * 北农商、银联快捷、代付签名工具类
 * 
 * @author GXL
 * @version v1.0
 */
public class SignatureUtil {

	/**
	 * 签名算法
	 *
	 * @param map
	 * @param key
	 * @param log
	 * @return
	 */
	public static String getSign(Map<String, Object> map, String key, Logger log) {
		ArrayList<String> list = new ArrayList<String>();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			if (entry.getValue() != null
					&& StringUtils.isNotBlank(String.valueOf(entry.getValue()))) {
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result = result.substring(0, result.length() - 1);
		result += key;
		log.info("Sign Before MD5: {}", result);
		result = Md5.getMD5Str(result).toUpperCase();
		log.info("Sign Result: {}", result);
		return result;
	}

	/**
	 * 从API返回的JSON数据里面重新计算一次签名
	 *
	 * @param responseString
	 *            API返回的JSON数据
	 * @return 新鲜出炉的签名
	 */
	@SuppressWarnings("unchecked")
	public static String getSignFromResponseString(String responseString,
			String key, Logger log) {
		Map<String, Object> map = JSONUtilsEx.deserialize(responseString,
				Map.class);
		// 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.put("sign", "");
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		return SignatureUtil.getSign(map, key, log);
	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 *
	 * @param responseString
	 *            API返回的JSON数据字符串
	 * @return API签名是否合法
	 */
	@SuppressWarnings("unchecked")
	public static boolean checkSign(String responseString, String key,
			Logger log) {

		Map<String, Object> map = JSONUtilsEx.deserialize(responseString,
				HashMap.class);
		log.info("校验签名的数据:{}", map.toString());

		String signFromAPIResponse = map.get("sign").toString();
		if (signFromAPIResponse == "" || signFromAPIResponse == null) {
			log.info("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		log.info("服务器回包里面的签名是:{}", signFromAPIResponse);
		// 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.put("sign", "");
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		String signForAPIResponse = SignatureUtil.getSign(map, key, log);

		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			log.info("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		log.info("恭喜，API返回的数据签名验证通过!!!");
		return true;
	}

	/**
	 * 校验签名
	 *
	 * @param map
	 * @param key
	 * @param log
	 * @return
	 */
	public static boolean checkSign(Map<String, Object> map, String key,
			Logger log) {
		log.info("校验签名的数据:{}", map.toString());

		String signFromAPIResponse = map.get("sign").toString();
		if (signFromAPIResponse == "" || signFromAPIResponse == null) {
			log.info("API返回的数据签名数据不存在，有可能被第三方篡改!!!");
			return false;
		}
		log.info("服务器回包里面的签名是:{}", signFromAPIResponse);
		// 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.remove("sign");
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		String signForAPIResponse = SignatureUtil.getSign(map, key, log);

		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			log.info("API返回的数据签名验证不通过，有可能被第三方篡改!!!");
			return false;
		}
		log.info("恭喜，API返回的数据签名验证通过!!!");
		return true;
	}

	/**
	 * 构建签名原文
	 * 
	 * @param signFilds
	 *            参数列表
	 * @param param
	 *            参数与值的map
	 * @return
	 */
	public static String orgSignSrc(String[] signFields, Map<String, Object> param) {
		if (signFields != null) {
			Arrays.sort(signFields); // 对key按照 字典顺序排序
		}

		StringBuffer signSrc = new StringBuffer("");
		int i = 0;
		for (String field : signFields) {
			if(param.get(field) != null && !"".equals(String.valueOf(param.get(field)))){
				signSrc.append(field);
				signSrc.append("=");
				signSrc.append(String.valueOf(param.get(field)));
				// 最后一个元素后面不加&
				if (i < (signFields.length - 1)) {
					signSrc.append("&");
				}
			}
			i++;
		}
		
		String str = signSrc.toString();
		
		char s = str.charAt(str.length()-1);
		
		if("&".equals(String.valueOf(s))){
			str = str.substring(0, str.length() - 1);
		}
		
		return str;
	}

	/**
	 * BASE64解密
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static byte[] decryptBASE64(String key) throws Exception {
		return (new BASE64Decoder()).decodeBuffer(key);
	}

	/**
	 * BASE64加密
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static String encryptBASE64(byte[] key) throws Exception {
		return (new BASE64Encoder()).encodeBuffer(key);
	}

	
	/**
	 * 银联快捷通道签名
	 * @param data
	 * @param privateKey
	 * @return
	 * @throws Exception
	 */
	public static String sign(byte[] data, String privateKey) throws Exception {
		// 解密由base64编码的私钥
		byte[] keyBytes = decryptBASE64(privateKey);

		// 构造PKCS8EncodedKeySpec对象
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);

		// KEY_ALGORITHM 指定的加密算法
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");

		// 取私钥匙对象
		PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);

		// 用私钥对信息生成数字签名
		Signature signature = Signature.getInstance("MD5withRSA");
		signature.initSign(priKey);
		signature.update(data);

		return encryptBASE64(signature.sign());
	}

	
	/**
	 * 银联快捷通道验签
	 * @param data
	 * @param publicKey
	 * @param sign
	 * @return
	 * @throws Exception
	 */
	public static boolean verify(byte[] data, String publicKey, String sign)
			throws Exception {

		// 解密由base64编码的公钥
		byte[] keyBytes = decryptBASE64(publicKey);

		// 构造X509EncodedKeySpec对象
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);

		// KEY_ALGORITHM 指定的加密算法
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");

		// 取公钥匙对象
		PublicKey pubKey = keyFactory.generatePublic(keySpec);

		Signature signature = Signature.getInstance("MD5withRSA");
		signature.initVerify(pubKey);
		signature.update(data);

		// 验证签名是否正常
		return signature.verify(decryptBASE64(sign));
	}

}
